package doc.jmidifile;

import java.io.Serializable;
import java.util.Arrays;

public class JMidiEvent implements Serializable {
    public static final int TYPE_INVALID = -1;
    public static final int TYPE_META = 0xff;
    public static final int TYPE_SYSEX0 = 0xf0;
    public static final int TYPE_SYSEX1 = 0xf7;
    public static final int TYPE_NOTE_ON = 0x90;
    public static final int TYPE_NOTE_OFF = 0x80;
    public static final int TYPE_AFTER_TOUCH = 0xa0; // 颤音
    public static final int TYPE_CONTROLLER = 0xb0; // 控制器，例如踏板
    public static final int TYPE_TIMBRE = 0xc0; // 音色控制
    public static final int TYPE_PRESSURE = 0xd0; // 通道压力控制。可以想像为整个通道同步的 aftertouch
    public static final int TYPE_PITCH_BEND = 0xe0; // 弯音轮

    public static final int TYPE_META_TEXT = 0x1;
    public static final int TYPE_META_COPYRIGHT = 0x2;
    public static final int TYPE_META_TRACK_NAME = 0x3;
    public static final int TYPE_META_INSTRUMENT_NAME = 0x4;
    public static final int TYPE_META_LYRIC = 0x5;
    public static final int TYPE_META_MAKER = 0x6;
    public static final int TYPE_META_CUE = 0x7;
    public static final int TYPE_META_PATCH_NAME = 0x8;
    public static final int TYPE_META_PORT_NAME = 0x9;
    public static final int TYPE_META_OUTPUT_PORT = 0x21;
    public static final int TYPE_META_TEMPO = 0x51;
    public static final int TYPE_META_SMTPE_OFFSET = 0x54;
    public static final int TYPE_META_TIME_SIGN = 0x58;
    public static final int TYPE_META_KEY_SIGN = 0x59;
    public static final int TYPE_META_CHANNEL_PREFIX = 0x20;
    public static final int TYPE_META_TRACK_END = 0x2f;
    public static final int TYPE_META_SEQUENCER_SPECIFIC = 0x7f;
    // tick 或者 ms 小于 0，表示未初始化
    public int tick = -1;
    public long ms = -1;
    // 注意 data 可能为 null
    public byte[] data;

    public JMidiEvent(int tick) {
        this.tick = tick;
    }

    public JMidiEvent(long ms) {
        this.ms = ms;
    }

    public JMidiEvent( byte... data) {
        this.data = data;
    }

    public JMidiEvent(int tick,  byte... data) {
        this.tick = tick;
        this.data = data;
    }

    public JMidiEvent(long ms,  byte... data) {
        this.ms = ms;
        this.data = data;
    }

    public int getEventType() {
        if (data == null) return TYPE_INVALID;
        int s = data[0] & 0xff;
        return (s < 0xf0) ? (s & 0xf0) : s;
    }

    public int getMetaType() {
        if (data == null) return TYPE_INVALID;
        return data[1] & 0xff;
    }

    public void setNoteOn(int chan, int pitch, int vel) {
        data = new byte[]{(byte) (TYPE_NOTE_ON | (0xf & chan)), (byte) (pitch & 0x7f), (byte) (vel & 0x7f)};
    }

    public void setNoteOff(int chan, int pitch) {
        setNoteOn(chan, pitch, 0);
    }

    // timbre 音色控制
    public void setTimbre(int chan, int num) {
        data = new byte[]{(byte) (TYPE_TIMBRE | (0xf & chan)), (byte) (0x7f & num)};
    }

    // meta event
    public void setMeta(int metaType, final byte... data) {
        if (data == null) {
            this.data = new byte[]{(byte) TYPE_META, (byte) metaType};
        } else {
            this.data = new byte[2 + data.length];
            this.data[0] = (byte) TYPE_META;
            this.data[1] = (byte) metaType;
            System.arraycopy(data, 0, this.data, 2, data.length);
        }
    }

    /**
     * 获取 meta 信息中的数据
     *
     * @return
     */
    public byte[] getMetaData() {
        return Arrays.copyOfRange(data, 2, data.length);
    }

    public void setText(final byte... data) {
        setMeta(TYPE_META_TEXT, data);
    }

    public byte[] getText() {
        return getMetaData();
    }

    public void setTrackEnd() {
        setMeta(TYPE_META_TRACK_END, null);
    }

    /**
     * 设置拍速，拍速的单位为 1/4 小节
     *
     * @param bpm
     */
    public void setBPM(int bpm) {
        setMeta(TYPE_META_TEMPO, JMidiUtils.bpmToMpp(bpm, 4));
    }

    /**
     * 获取以 1/4 小节为一拍的拍速
     */
    public int getBPM() {
        return JMidiUtils.mppToBpm(getMetaData(), 4);
    }

    /**
     * 设置拍号
     *
     * @param top    每小节多少拍
     * @param bottom 以几分之一小节为一拍
     */
    public void setTimeSign(int top, int bottom) {
        int base2 = 0;
        while ((bottom >>= 1) > 0) {
            base2++;
        }
        setMeta(TYPE_META_TIME_SIGN, (byte) (top & 0xff), (byte) (base2 & 0xff), (byte) 24, (byte) 8);
    }

    /**
     * 获取拍号
     *
     * @return [top, bottom]
     */
    public int[] getTimeSign() {
        byte[] data = getMetaData();
        int[] ret = new int[2];
        ret[0] = data[0] & 0xff;
        ret[1] = 1;
        int i = data[1] & 0xff;
        while (i > 0) {
            ret[1] <<= 1;
            i -= 1;
        }
        return ret;
    }

    // 判定这是否是一个音符事件
    public boolean isNote() {
        int t = getEventType();
        return t == TYPE_NOTE_ON || t == TYPE_NOTE_OFF;
    }

    /**
     * 获取音符事件的 pitch 值
     */
    public int getPitch() {
        return data[1] & 0xff;
    }

    /**
     * 获取音符事件的力度值
     *
     * @return > 0 表示 note on
     */
    public int getVelocity() {
        if (getEventType() == TYPE_NOTE_ON) return data[2] & 0xff;
        return 0;
    }

    public int getTimbre() {
        return data[1] & 0xff;
    }
}
